package utils

import (
	"bytes"
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/base64"
	"encoding/hex"
	"encoding/pem"
	"errors"
	"fmt"
	"gitee.com/zxs-micro/zxs-micro-common/log"
	"github.com/spf13/viper"
	"io/ioutil"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

//验证x509证书链，包括用户证书、中间证书和根证书的验证
func VerifyX509CertChain(c *x509.Certificate, cas []*x509.Certificate) error {
	now := time.Now().UTC()
	if now.After(c.NotAfter) {
		return fmt.Errorf("certificate has expired, now: %v, cert.NotAfter: %v", now, c.NotAfter)
	}
	var err error
	for _, ca := range cas {
		err = c.CheckSignatureFrom(ca)
		if err == nil {
			err = ca.CheckSignatureFrom(ca)
			if err == nil { //root ca
				return nil
			} else {
				return VerifyX509CertChain(ca, cas)
			}
		}
	}
	return err
}

//创建私钥和证书请求
func GenPkcs10(name, ou, o, location, state, country, address, postcode, alg string) (pemPrikey, pkcs10 string, err error) {
	//生成私钥
	var rsaPrivateKey *rsa.PrivateKey
	var ecdsaPrivateKey *ecdsa.PrivateKey

	switch alg {
	case "rsa1024":
		rsaPrivateKey, err = rsa.GenerateKey(rand.Reader, 1024)
	case "rsa2048":
		rsaPrivateKey, err = rsa.GenerateKey(rand.Reader, 2048)
	case "ecdsa256":
		ecdsaPrivateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	default:
		return "", "", fmt.Errorf("alg invalid")
	}
	if err != nil {
		return "", "", fmt.Errorf("generate private key failed: %s", err.Error())

	}

	var priKeyDerStream []byte
	switch alg {
	case "rsa1024", "rsa2048":
		priKeyDerStream = x509.MarshalPKCS1PrivateKey(rsaPrivateKey)
	case "ecdsa256":
		priKeyDerStream, err = x509.MarshalECPrivateKey(ecdsaPrivateKey)
		if err != nil {
			return "", "", fmt.Errorf("marshal privateKey failed: %s", err.Error())
		}
	}

	//编码私钥
	block := &pem.Block{
		Type:  "PRIVATE KEY",
		Bytes: priKeyDerStream,
	}
	buffer := bytes.NewBuffer(make([]byte, 0))
	err = pem.Encode(buffer, block)
	if err != nil {
		return "", "", fmt.Errorf("pem encode failed: %s", err.Error())
	}
	pemPrikey = buffer.String()

	log.Log.Debugf("signPriKey: \n%s", pemPrikey)

	//产生证书请求
	subject := pkix.Name{
		Country:            []string{country},
		Organization:       []string{o},
		OrganizationalUnit: []string{ou},
		Locality:           []string{location},
		Province:           []string{state},
		StreetAddress:      []string{address},
		PostalCode:         []string{postcode},
		CommonName:         name,
	}

	req := &x509.CertificateRequest{
		Subject: subject,
	}

	var pkcs10DerStream []byte
	switch alg {
	case "rsa1024", "rsa2048":
		pkcs10DerStream, err = x509.CreateCertificateRequest(rand.Reader, req, rsaPrivateKey)
	case "ecdsa256":
		pkcs10DerStream, err = x509.CreateCertificateRequest(rand.Reader, req, ecdsaPrivateKey)
	}
	if err != nil {
		return "", "", fmt.Errorf("CreateCertificateRequest failed: %s", err.Error())
	}

	pkcs10 = base64.StdEncoding.EncodeToString(pkcs10DerStream)
	log.Log.Debugf("pkcs10: \n%s", pkcs10)

	return
}

func GetPubKeyByteFromCert(pemCert string) ([]byte, error) {
	block, _ := pem.Decode([]byte(pemCert))
	if block == nil {
		return nil, errors.New("block is nil")
	}

	cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		return nil, errors.New("ParseCertificate faild")
	}

	pubKeyStream, _ := x509.MarshalPKIXPublicKey(cert.PublicKey)

	return pubKeyStream[27:], nil
}

//pem字符串转为x509证书
func ParsePem2Cert(pemCert string) (*x509.Certificate, error) {
	block, _ := pem.Decode([]byte(pemCert))
	if block == nil {
		log.Log.Errorf("failed to decode pem cert")
		return nil, errors.New("failed to decode pem cert")
	}
	cert, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		log.Log.Errorf("ParseCertificate fail: %s", err.Error())
		return nil, err
	}

	return cert, nil
}

//取自定义的证书扩展
func GetSelfDefineCertExt(cert string) ([]Extension, error) {
	x509Cert, err := ParsePem2Cert(cert)
	if err != nil {
		return nil, fmt.Errorf("ParseCertificate fail: %s", err.Error())
	}

	if x509Cert.Extensions == nil {
		return nil, nil
	}

	//解析扩展值
	var configs []CertExtConfig
	err = viper.UnmarshalKey("cert.certExts", &configs)
	if err != nil {
		return nil, fmt.Errorf("unmarshal cert extension config fail: %s", err.Error())
	}

	var extensions []Extension
	for _, config := range configs {
		for _, ext := range x509Cert.Extensions {
			if ext.Id.String() == config.OID {
				e := Extension{
					Name:  config.Name,
					Value: string(ext.Value),
				}
				extensions = append(extensions, e)
				break
			}
		}
	}

	return extensions, nil
}

type CertExtConfig struct {
	Name        string         `yaml:"name" json:"name"`
	OID         string         `yaml:"oid" json:"oid"`
	DisplayName string         `yaml:"displayName" json:"display_name"`
	Values      []CertExtValue `yaml:"values, omitempty" json:"values,omitempty"`
}
type CertExtValue struct {
	Value       string `yaml:"value" json:"value"`
	DisplayName string `yaml:"displayName" json:"display_name"`
}
type Extension struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

var basicCertExt = []Extension{
	{Name: "basicConstraints", Value: "CA:FALSE"},
	{Name: "keyUsage", Value: "nonRepudiation, digitalSignature, keyEncipherment"},
	{Name: "subjectKeyIdentifier", Value: "hash"},
	{Name: "authorityKeyIdentifier", Value: "keyid,issuer"},
	{Name: "extendedKeyUsage", Value: "serverAuth,clientAuth"}}

//输入openssl.cnf，证书存放路径,CA私钥所在路径和CA证书所在路径，证书请求，证书扩展项和有效期。有效期单位为天
//本方法需要在linux平台下使用openssl指令
func OpensslCreateCert(configFile, filePath, caKey, caFile string, req []byte, exts []Extension, validity int) (string, error) {
	var err error

	t := strconv.FormatInt(time.Now().UnixNano(), 10)
	reqFileName := filepath.Join(filePath, "opensslReq"+t+".pem")
	certFileName := filepath.Join(filePath, "opensslCert"+t+".pem")

	err = ioutil.WriteFile(reqFileName, req, 0600)
	if err != nil {
		return "", fmt.Errorf("write req to file %s failed: %s", reqFileName, err.Error())
	}

	//生成证书
	arg := fmt.Sprintf("openssl ca -keyfile %s -cert %s -in %s -out %s -days %d -config %s -batch",
		caKey, caFile, reqFileName, certFileName, validity, configFile)
	if exts != nil {
		var strExt string
		for _, ext := range exts {
			if ext.Name == "subjectAltName" {
				strExt = fmt.Sprintf("%s %s=%s\n", strExt, ext.Name, ext.Value)
			} else {
				hexValue := hex.EncodeToString([]byte(ext.Value))
				strExt = fmt.Sprintf("%s %s=DER:%s\n", strExt, ext.Name, hexValue)
			}
		}
		for _, ext := range basicCertExt {
			strExt = fmt.Sprintf("%s %s=%s\n", strExt, ext.Name, ext.Value)
		}
		arg0 := fmt.Sprintf("echo \"%s\" > extfile.cnf", strExt)
		cmd := exec.Command("/bin/sh", "-c", arg0)
		out, err := cmd.CombinedOutput()
		if err != nil {
			return "", fmt.Errorf("exec cmd: %s failed: %s", arg, string(out))
		}

		arg = fmt.Sprintf("%s -extfile extfile.cnf", arg)
	}
	log.Log.Debugf("arg: %s", arg)
	cmd := exec.Command("/bin/sh", "-c", arg)
	out, err := cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("exec cmd: %s failed: %s", arg, string(out))
	}

	bcert, err := ioutil.ReadFile(certFileName)
	if err != nil {
		return "", fmt.Errorf("read file %s failed: %s", certFileName, err.Error())
	}

	//删除临时文件
	arg = "rm -rf " + certFileName
	cmd = exec.Command("/bin/sh", "-c", arg)
	out, err = cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("exec cmd: %s failed: %s", arg, string(out))
	}

	arg = "rm -rf " + reqFileName
	cmd = exec.Command("/bin/sh", "-c", arg)
	out, err = cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("exec cmd: %s failed: %s", arg, string(out))
	}

	cmd = exec.Command("/bin/sh", "-c", "rm -rf extfile.cnf")
	out, err = cmd.CombinedOutput()
	if err != nil {
		return "", fmt.Errorf("exec cmd: %s failed: %s", arg, string(out))
	}

	scert := string(bcert)

	return scert[strings.Index(scert, "-----BEGIN CERTIFICATE-----"):], nil
}
